﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Roslyn.Services;
using Roslyn.Compilers;
using System.Threading.Tasks;
using System.Threading;
using Roslyn.Compilers.Common;
using SelectionAddin.Extension;
using Roslyn.Compilers.CSharp;
using RoslynProcess;
using System.IO;
using CompleterLib.Util;
using CompleterLib;

namespace CompleterProcess
{
    public class Program : RoslynProcess.RoslynProcess
    {
        static void Main(string[] args)
        {
            string baseDir = args[0];
            BaseDirectoryUtil.ExtensionDirectory = baseDir;
            new Program().MainLoop();
        }

        private class ActiveCompletion
        {
            readonly string token;
            public CancellationTokenSource CancellationSource { get; set; }
            public IEnumerator<Completion> Completions {get; set; }

            public ActiveCompletion(string token)
            {
                this.token = token;
                this.OutputtedCompletions = 0;
            }

            public string Token { get { return token; } }

            public int OutputtedCompletions { get; set; }
        }
        private Dictionary<string, ActiveCompletion> activeCompletions =
            new Dictionary<string, ActiveCompletion>();

        protected override bool HandleCommand(string cmd, string args)
        {
            try
            {
                if (base.HandleCommand(cmd, args))
                {
                    return true;
                }
                else if (cmd.Equals("Complete"))
                {
                    string[] argsArr = args.Split(new char[] { ' ' }, 6);
                    int startLineNum = int.Parse(argsArr[0]);
                    int endLineNum = int.Parse(argsArr[2]);
                    int startCol = int.Parse(argsArr[1]);
                    int endCol = int.Parse(argsArr[3]);
                    ActiveCompletion ac = new ActiveCompletion(argsArr[4]); // Unique token used to identify this completion request
                    activeCompletions[ac.Token] = ac;
                    string query = null;
                    if (argsArr.Length == 6)
                    {
                        query = argsArr[5];
                    }
                    IText text = CurrentDocument.GetText();

                    var startLine = text.GetLineFromLineNumber(startLineNum);
                    int start = startLine.Start + startCol;

                    var endLine = text.GetLineFromLineNumber(endLineNum);
                    int end = endLine.Start + endCol;

                    TextSpan span = new TextSpan(start, end - start);

                    if (query != null)
                    {
                        TextSpan? spanTmp = InsertQueryAt(query, span);
                        if (!spanTmp.HasValue)
                        {
                            WriteLine("MATCH_NOT_PARTIAL: " + ac.Token);
                            return true;
                        }
                        else
                        {
                            span = spanTmp.Value;
                        }
                    }

                    SemanticModel semModel = (SemanticModel)CurrentDocument.GetSemanticModel();
                    ac.CancellationSource = new CancellationTokenSource();
                    CancellationToken token = ac.CancellationSource.Token;

                    var match = GetBestMatch(span, token)
                               .ExpandMatch(span.Length != 0, semModel);
                    var matchLine = text.GetLineFromPosition(match.Span.Start);
                    var matchCol = match.Span.Start - matchLine.Start;
                    var matchEndLine = text.GetLineFromPosition(match.Span.End);
                    var matchEndCol = match.Span.End - matchEndLine.Start;
                    WriteLine("MATCH_SPAN: " + ac.Token + " "
                        + matchLine.LineNumber + " " + matchCol + " "
                        + matchEndLine.LineNumber + " " + matchEndCol);

                    if (match is ExpressionSyntax)
                    {
                        TypeSymbol retType = null;
                        if (match is CastExpressionSyntax)
                        {
                            var cast = (CastExpressionSyntax)match;
                            retType = semModel.GetTypeInfo(cast.Type).Type;
                            if (retType.TypeKind.Equals(TypeKind.Error))
                            {
                                retType = null;
                            }
                        }
                        if (retType == null)
                        {
                            retType = ((ExpressionSyntax)match).GetRetType(semModel);
                        }
                        WriteLine("MATCH_TYPE: " + ac.Token + " "
                            + (retType.ToString() ?? "any"));
                    }

                    string rewrite;
                    bool isPartial = match.RewritePartial(semModel, out rewrite);
                    if (isPartial)
                    {
                        WriteLine("MATCH_REWRITE: " + ac.Token + " "
                            + rewrite);
                        ac.Completions = match.GetCompletions(semModel,
                                                              ac.CancellationSource.Token)
                                              .GetEnumerator();
                        new Task(() => DisplayCompletions(ac), ac.CancellationSource.Token).Start();
                    }
                    else
                    {
                        WriteLine("MATCH_NOT_PARTIAL: " + ac.Token);
                    }
                }
                else if (cmd.Equals("MoreCompletions"))
                {
                    string[] argsArr = args.Split(new char[] { ' ' }, 2);
                    ActiveCompletion ac;
                    if (activeCompletions.TryGetValue(argsArr[0], out ac))
                    {
                        new Task(() => DisplayCompletions(ac, (argsArr.Length == 2 ? (int?)int.Parse(argsArr[1]) : null)), ac.CancellationSource.Token).Start();
                    }
                }
                else if (cmd.Equals("Cancel"))
                {
                    ActiveCompletion ac;
                    if(activeCompletions.TryGetValue(args, out ac))
                    {
                        ac.CancellationSource.Cancel();
                        // Write something so process waiting for output will stop waiting.
                        WriteLine("CANCELLED " + ac.Token);
                        activeCompletions.Remove(ac.Token);
                    }
                }
                else
                {
                    return false;
                }
                return true;
            }
            catch (Exception e)
            {
                string on = "(" + cmd + " " + args + ") ";
                WriteLine("FAILURE: " + on + e);
                foreach (string line in e.StackTrace.Split('\n'))
                {
                    WriteLine("FAILURE: " + on + "TRACE: " + line);
                }
                throw;
            }
        }
        private TextSpan? InsertQueryAt(string query, TextSpan span)
        {
            if (CurrentDocument != null)
            {
                if (span.Length > 0)
                {
                    string sel = CurrentDocument.GetText().GetText(span);
                    var newStart = span.Start;
                    var newLen = span.Length;
                    var startDiff = sel.Length - sel.TrimStart().Length;
                    var endDiff = sel.Length - sel.TrimEnd().Length;
                    span = new TextSpan(newStart + startDiff, newLen - startDiff - endDiff);
                }

                CommonSyntaxNode root = CurrentDocument.GetSyntaxTree().GetRoot();
                var match = BestMatchUtil.GetClosestStatement(root, span);
                int? insertAt = null;
                if (match == null)
                {
                    insertAt = BestMatchUtil.FindFieldDeclPosition(root);
                }
                else
                {
                    insertAt = match is BlockSyntax
                            ? ((BlockSyntax)match).OpenBraceToken.FullSpan.End
                            : match.FullSpan.End;
                }
                if (insertAt.HasValue)
                {
                    string prefix = " object ___query___ = ";
                    InsertText(prefix + query, insertAt.Value);
                    return new TextSpan(insertAt.Value + prefix.Length, query.Length);
                }
                else
                {
                    return null;
                }
            }
            return null;
        }

        // Select the SyntaxNode / SyntaxToken / SyntaxTrivia whose span best matches the supplied span.
        public CommonSyntaxNode GetBestMatch(TextSpan span, CancellationToken token)
        {
            CommonSyntaxNode match = null;
            if (CurrentDocument != null)
            {
                if (span.Length > 0)
                {
                    string sel = CurrentDocument.GetText(token).GetText(span);
                    var newStart = span.Start;
                    var newLen = span.Length;
                    var startDiff = sel.Length - sel.TrimStart().Length;
                    var endDiff = sel.Length - sel.TrimEnd().Length;
                    span = new TextSpan(newStart + startDiff, newLen - startDiff - endDiff);
                }

                CommonSyntaxNode root = CurrentDocument.GetSyntaxTree(token).GetRoot();
                match = BestMatchUtil.GetBestMatch(root, span);
            }
            return match;
        }

        const int NUM_COMPLETIONS_AT_ONCE = 50;
        void DisplayCompletions(ActiveCompletion ac, int? lastSeen = null)
        {
            if (ac.CancellationSource.IsCancellationRequested)
            {
                return;
            }
            lock (ac.Completions)
            {
                bool hasNext = false;
                try
                {
                    var token = ac.CancellationSource.Token;
                    token.ThrowIfCancellationRequested();
                    int i;
                    for (i = 0; i < NUM_COMPLETIONS_AT_ONCE
                                    && (!lastSeen.HasValue
                                        || ac.OutputtedCompletions < lastSeen.Value + NUM_COMPLETIONS_AT_ONCE)
                                    && (hasNext = ac.Completions.MoveNext()); i++)
                    {
                        token.ThrowIfCancellationRequested();
                        WriteLine("COMPLETION: "
                            + ac.Token + " " + ac.Completions.Current);
                        ac.OutputtedCompletions++;
                    }
                    if (!hasNext && i > 0)
                    {
                        WriteLine("FINISHED_COMPLETIONS: " + ac.Token);
                    }
                }
                catch (OperationCanceledException)
                { }
                catch (Exception e)
                {
                    WriteLine("FAILURE: " + ac.Token + " " + e);
                    foreach (string line in e.StackTrace.Split('\n'))
                    {
                        WriteLine("FAILURE: " + ac.Token + " TRACE: " + line);
                    }
                    ac.Completions = null;
                    ac.CancellationSource.Cancel();
                }
            }
        }
    }
}
